package org.smartboot.compare.utils;

import org.smartboot.compare.FieldCache;
import org.smartboot.compare.TypeAssigner;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author qinluo
 * @version 1.0.0
 * @date 2021/2/4 1:43 上午
 */
public final class InternalClassUtils {

    private static final List<Class<?>> SIMPLE_CLASS = new ArrayList<>(8);
    // Primitive mapping wrapper class
    private static final Map<Class<?>, Class<?>> PMW = new HashMap<>(8);

    private static final TypeAssigner MAP_ASSIGNER = new TypeAssigner(true, Map.class);
    private static final TypeAssigner SET_ASSIGNER = new TypeAssigner(true, Set.class);
    private static final TypeAssigner LIST_ASSIGNER = new TypeAssigner(true, List.class);

    /**
     * Field Caches.
     */
    private static final Map<Class<?>, List<FieldCache>> FIELD_CACHE = new ConcurrentHashMap<>(128);

    static {
        PMW.put(byte.class, Byte.class);
        PMW.put(short.class, Short.class);
        PMW.put(int.class, Integer.class);
        PMW.put(long.class, Long.class);
        PMW.put(float.class, Float.class);
        PMW.put(double.class, Double.class);
        PMW.put(char.class, Character.class);
        PMW.put(boolean.class, Boolean.class);

        PMW.forEach((k, v) -> {
            SIMPLE_CLASS.add(k);
            SIMPLE_CLASS.add(v);
        });

        SIMPLE_CLASS.add(Object.class);
    }

    public static <T> Class<T> getWrapper(Class<?> p) {
        return (Class<T>) PMW.get(p);
    }

    public static boolean isSimple(Class<?> clz) {
        return SIMPLE_CLASS.contains(clz);
    }

    public static boolean isCollection(Class<?> clz) {
        return LIST_ASSIGNER.checkCast(clz) || SET_ASSIGNER.checkCast(clz) || MAP_ASSIGNER.checkCast(clz);
    }

    public static Class<?> lookupHighestType(Class<?> clz) {
        if (!isCollection(clz)) {
            return clz;
        }

        if (LIST_ASSIGNER.checkCast(clz)) {
            return List.class;
        }

        if (MAP_ASSIGNER.checkCast(clz)) {
            return Map.class;
        }

        return Set.class;
    }

    public static List<FieldCache> getAllFields(Class<?> clz) {
        List<FieldCache> fields = FIELD_CACHE.get(clz);
        if (fields != null) {
            return fields;
        }

        List<Field> allFields = new ArrayList<>(128);
        Class<?> waitProcessClz = clz;
        while (waitProcessClz != Object.class) {
            addElements(allFields, waitProcessClz.getDeclaredFields());
            waitProcessClz = waitProcessClz.getSuperclass();
        }

        // filter static fields
        List<FieldCache> fieldCaches = allFields.stream().filter(p -> !Modifier.isStatic(p.getModifiers()) && !p.isSynthetic() && !Modifier.isTransient(p.getModifiers()))
                                    .map(FieldCache::new).collect(Collectors.toList());
        FIELD_CACHE.put(clz, fieldCaches);

        return fieldCaches;
    }

    private static <T> void addElements(List<T> list, T[] fields) {
        if (list == null || fields == null) {
            return;
        }
        Collections.addAll(list, fields);
    }
}
